// attract positive repel negative
package traer.physics;

/**
 * @author Jeffrey Traer Bernstein, modifications by Roland Haring <P>
 * <a href="http://www.cs.princeton.edu/~traer/physics/" target="blank">http://www.cs.princeton.edu/~traer/physics/</a>
 */
public class Attraction implements Force {

    Particle a;
    Particle b;
    float k;
    boolean on;
    float distanceMin;
    float distanceMinSquared;
    float distanceMaxSquared;

    public Attraction(Particle a, Particle b, float k, float distanceMin, float distanceMax) {
        this.a = a;
        this.b = b;
        this.k = k;
        on = true;
        this.distanceMin = (distanceMin == 0.0f ? Float.MIN_VALUE : distanceMin);
        this.distanceMinSquared = this.distanceMin * this.distanceMin;
        this.distanceMaxSquared = distanceMax * distanceMax;
    }

	public void apply() {
        if (on && (a.isFree() || b.isFree())) {
            float a2bX = a.position().x() - b.position().x();
            float a2bY = a.position().y() - b.position().y();
            float a2bZ = a.position().z() - b.position().z();

            float a2bDistanceSquared = a2bX * a2bX + a2bY * a2bY + a2bZ * a2bZ;

            if (a2bDistanceSquared > distanceMaxSquared) {
                return;
            }

            if (a2bDistanceSquared < distanceMinSquared) {
                a2bDistanceSquared = distanceMinSquared;
            }

            if (a2bDistanceSquared == 0.0f) {
                a2bDistanceSquared = Float.MIN_VALUE;
            }

            float force = k * a.mass * b.mass / a2bDistanceSquared;

            if (Float.isInfinite(force)) {
                force = Float.MAX_VALUE;
            }

            float length = (float) Math.sqrt(a2bDistanceSquared);

            // make unit vector

            a2bX /= length;
            a2bY /= length;
            a2bZ /= length;

            // multiply by force

            a2bX *= force;
            a2bY *= force;
            a2bZ *= force;

            final float MIN_FORCE = 0.0001f;
            if (Math.abs(a2bX) < MIN_FORCE &&
                Math.abs(a2bY) < MIN_FORCE &&
                Math.abs(a2bZ) < MIN_FORCE) {
                return;
            }

            // apply

            if (a.isFree()) {
                a.force().add(-a2bX, -a2bY, -a2bZ);
                a.setAttractionInfluenced(true);
            }
            if (b.isFree()) {
                b.force().add(a2bX, a2bY, a2bZ);
                b.setAttractionInfluenced(true);
            }
        }
    }

    public boolean contains(Particle p) {
        return p == a || p == b;
    }

    public final float getMinimumDistance() {
        return distanceMin;
    }

    public final Particle getOneEnd() {
        return a;
    }

    public final float getStrength() {
        return k;
    }

    public final Particle getTheOtherEnd() {
        return b;
    }

	public final boolean isOff() {
        return !on;
    }

	public final boolean isOn() {
        return on;
    }

    protected void setA(Particle p) {
        a = p;
    }

    protected void setB(Particle p) {
        b = p;
    }

    public final void setMinimumDistance(float d) {
        distanceMin = d;
        distanceMinSquared = d * d;
    }

    public final void setStrength(float k) {
        this.k = k;
    }

	public final void turnOff() {
        on = false;
    }

	public final void turnOn() {
        on = true;
    }
}
